/*
 * Copyright 2016 Kevin Herron
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.digitalpetri.modbus.examples.master;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Timer;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import com.digitalpetri.modbus.codec.Modbus;
import com.digitalpetri.modbus.master.ModbusTcpMaster;
import com.digitalpetri.modbus.master.ModbusTcpMasterConfig;
import com.digitalpetri.modbus.requests.*;
import com.digitalpetri.modbus.responses.*;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.util.ReferenceCountUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MasterExample {

    public static void main(String[] args) {
        new MasterExample(1, 1, 0, 1, 7).start();
    }

    private final Logger logger = LoggerFactory.getLogger(getClass());

    //线圈离散输入字节位
    private static final int BYTE_BIT = 8;

    //单线程调度执行器
    private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();

    //声明一个线程安全的变体 ArrayList
    private final List<ModbusTcpMaster> masters = new CopyOnWriteArrayList<>();
    private volatile boolean started = false;

    private final int nMasters;
    private final int nRequests;
    private final int address;
    private final int quantity;
    private final int functionCode;

    public MasterExample(int nMasters, int nRequests, int address, int quantity, int functionCode) {
        this.nMasters = nMasters;
        this.nRequests = nRequests;
        this.address = address;
        this.quantity = quantity;
        this.functionCode = functionCode;
    }

    public void start() {
        started = true;

        ModbusTcpMasterConfig config = new ModbusTcpMasterConfig.Builder("localhost")
                .setPort(502)
                .build();

        new Thread(() -> {
            while (started) {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                double mean = 0.0;
                double oneMinute = 0.0;

                for (ModbusTcpMaster master : masters) {
                    mean += master.getResponseTimer().getMeanRate();
                    oneMinute += master.getResponseTimer().getOneMinuteRate();
                }

                logger.info("Mean rate={}, 1m rate={}", mean, oneMinute);
            }
        }).start();

        for (int i = 0; i < nMasters; i++) {
            ModbusTcpMaster master = new ModbusTcpMaster(config);
            master.connect();

            masters.add(master);

            for (int j = 0; j < nRequests; j++) {
                sendAndReceive(master);
            }
        }
    }

    private void sendAndReceive(ModbusTcpMaster master) {
        if (!started) {
            return;
        }

        switch (functionCode) {
            case 1:
                /*============================================读取线圈方法============================================*/
                CompletableFuture<ReadCoilsResponse> future1 =
                        master.sendRequest(new ReadCoilsRequest(address, quantity), 0);

                future1.whenCompleteAsync((response, ex) -> {
                    if (response != null) {
                        System.out.println("=====================读取线圈=====================");
                        String str = ByteBufUtil.hexDump(response.getCoilStatus());
                        //16 进制数组分割
                        String[] strings = stringToStringArray("0000", 2);
                        System.out.println("Response: " + Arrays.toString(strings));
                        //转化为所需要的 2 进制数组
                        String[] binaries = new String[strings.length];
                        for (int i = 0; i < strings.length; i++) {
                            //16 进制转 2 进制
                            String binaryString = Integer.toBinaryString(Integer.parseInt(strings[i], 16));
                            if (strings.length == 1) {
                                // 二进制位小于寄存器数量，则高位补 0
                                while (binaryString.length() < quantity) {
                                    StringBuilder builder = new StringBuilder();
                                    builder.append("0").append(binaryString);
                                    binaryString = builder.toString();
                                }
                            } else if (i < (strings.length - 1)) {
                                //二进制位小于 8 位，则高位补 0
                                while (binaryString.length() < BYTE_BIT) {
                                    StringBuilder builder = new StringBuilder();
                                    builder.append("0").append(binaryString);
                                    binaryString = builder.toString();
                                }
                            } else {
                                // 二进制位小于寄存器数量 - 8 位，则高位补 0
                                while (binaryString.length() < quantity - (i * BYTE_BIT)) {
                                    StringBuilder builder = new StringBuilder();
                                    builder.append("0").append(binaryString);
                                    binaryString = builder.toString();
                                }
                                binaryString = "";
                            }
                            binaries[i] = binaryString;
                        }
                        //数组反转
                        Collections.reverse(Arrays.asList(binaries));
                        //元素合并
                        StringBuilder builder = new StringBuilder();
                        for (String s : binaries) {
                            builder.append(s);
                        }
                        //字符串转数组
                        String[] stringArray = stringToStringArray(builder.reverse().toString(), 1);
                        for (String s : stringArray) {
                            System.out.println("Response Value = " + s);
                        }
                        ReferenceCountUtil.release(response);
                    } else {
                        logger.error("Completed exceptionally, message={}", ex.getMessage(), ex);
                    }
                    scheduler.schedule(() -> sendAndReceive(master), 5, TimeUnit.SECONDS);
                }, Modbus.sharedExecutor());
                break;
            case 2:
                /*==========================================读取离散输入方法==========================================*/
                CompletableFuture<ReadDiscreteInputsResponse> future2 =
                        master.sendRequest(new ReadDiscreteInputsRequest(address, quantity), 0);

                future2.whenCompleteAsync((response, ex) -> {
                    if (response != null) {
                        System.out.println("=====================读取离散输入=====================");
                        String str = ByteBufUtil.hexDump(response.getInputStatus());
                        //16 进制数组分割
                        String[] strings = stringToStringArray(str, 2);
                        System.out.println("Response: " + Arrays.toString(strings));
                        //转化为所需要的 2 进制数组
                        String[] binaries = new String[strings.length];
                        for (int i = 0; i < strings.length; i++) {
                            //16 进制转 2 进制
                            String binaryString = Integer.toBinaryString(Integer.parseInt(strings[i], 16));
                            if (strings.length == 1) {
                                // 二进制位小于寄存器数量，则高位补 0
                                while (binaryString.length() < quantity) {
                                    StringBuilder builder = new StringBuilder();
                                    builder.append("0").append(binaryString);
                                    binaryString = builder.toString();
                                }
                            } else if (i < (strings.length - 1)) {
                                //二进制位小于 8 位，则高位补 0
                                while (binaryString.length() < BYTE_BIT) {
                                    StringBuilder builder = new StringBuilder();
                                    builder.append("0").append(binaryString);
                                    binaryString = builder.toString();
                                }
                            } else {
                                // 二进制位小于寄存器数量 - 8 位，则高位补 0
                                while (binaryString.length() < quantity - (i * BYTE_BIT)) {
                                    StringBuilder builder = new StringBuilder();
                                    builder.append("0").append(binaryString);
                                    binaryString = builder.toString();
                                }
                            }
                            binaries[i] = binaryString;
                        }
                        //数组反转
                        Collections.reverse(Arrays.asList(binaries));
                        //元素合并
                        StringBuilder builder = new StringBuilder();
                        for (String s : binaries) {
                            builder.append(s);
                        }
                        //字符串转数组
                        String[] stringArray = stringToStringArray(builder.reverse().toString(), 1);
                        for (String s : stringArray) {
                            System.out.println("Response Value = " + s);
                        }
                        ReferenceCountUtil.release(response);
                    } else {
                        logger.error("Completed exceptionally, message={}", ex.getMessage(), ex);
                    }
                    scheduler.schedule(() -> sendAndReceive(master), 5, TimeUnit.SECONDS);
                }, Modbus.sharedExecutor());
                break;
            case 3:
                /*=========================================读取保持寄存器方法=========================================*/
                //代表一个 CompletableFuture 任务
                CompletableFuture<ReadHoldingRegistersResponse> future3 =
                        master.sendRequest(new ReadHoldingRegistersRequest(address, quantity), 0);
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //whenCompleteAsync(执行动作，执行器用于异步执行)
                future3.whenCompleteAsync((response, ex) -> {
                    if (response != null) {
                        System.out.println("=====================读取保持寄存器=====================");
                        String str = ByteBufUtil.hexDump(response.getRegisters());
                        /*String[] strings = stringToStringArray(str, 4);
                        System.out.println("Response: " + Arrays.toString(strings));
                        for (String string : strings) {
                            System.out.println("Response Value = " + (Integer.parseInt(string, 16)));
                        }*/

                        //16 进制数组分割
                        String[] strings = stringToStringArray(str, 2);
                        System.out.println("Response: " + Arrays.toString(strings));
                        //转化为所需要的 2 进制数组
                        String[] binaries = new String[strings.length];
                        for (int i = 0; i < strings.length; i++) {
                            //16 进制转 2 进制
                            String binaryString = Integer.toBinaryString(Integer.parseInt(strings[i], 16));
                            if (strings.length == 1) {
                                // 二进制位小于寄存器数量，则高位补 0
                                while (binaryString.length() < quantity) {
                                    StringBuilder builder = new StringBuilder();
                                    builder.append("0").append(binaryString);
                                    binaryString = builder.toString();
                                }
                            } else if (i < (strings.length - 1)) {
                                //二进制位小于 8 位，则高位补 0
                                while (binaryString.length() < BYTE_BIT) {
                                    StringBuilder builder = new StringBuilder();
                                    builder.append("0").append(binaryString);
                                    binaryString = builder.toString();
                                }
                            } else {
                                // 二进制位小于寄存器数量 - 8 位，则高位补 0
                                while (binaryString.length() < quantity - (i * BYTE_BIT)) {
                                    StringBuilder builder = new StringBuilder();
                                    builder.append("0").append(binaryString);
                                    binaryString = builder.toString();
                                }
                            }
                            binaries[i] = binaryString;
                        }
                        //数组反转
                        Collections.reverse(Arrays.asList(binaries));
                        //元素合并
                        StringBuilder builder = new StringBuilder();
                        for (String s : binaries) {
                            builder.append(s);
                        }
                        //字符串转数组
                        String[] stringArray = stringToStringArray(builder.reverse().toString(), 1);
                        for (String s : stringArray) {
                            System.out.println("Response Value = " + s);
                        }

                        //有释放内存的作用
                        ReferenceCountUtil.release(response);
                    } else {
                        //打印一个异常的线程完成信息
                        logger.error("Completed exceptionally, message={}", ex.getMessage(), ex);
                    }
                    //创建并执行在给定延迟后启用的单次操作 (要执行的任务，从现在开始延迟执行的时，延时参数的时间单位)
                    scheduler.schedule(() -> sendAndReceive(master), 0, TimeUnit.SECONDS);
                }, Modbus.sharedExecutor());
                break;
            case 4:
                /*=========================================读取输入寄存器方法=========================================*/
                CompletableFuture<ReadInputRegistersResponse> future4 =
                        master.sendRequest(new ReadInputRegistersRequest(address, quantity), 0);

                future4.whenCompleteAsync((response, ex) -> {
                    if (response != null) {
                        System.out.println("=====================读取输入寄存器=====================");
                        String str = ByteBufUtil.hexDump(response.getRegisters());
                        String[] strings = stringToStringArray(str, 4);
                        System.out.println("Response: " + Arrays.toString(strings));
                        for (String string : strings) {
                            System.out.println("Response Value = " + Integer.parseInt(string, 16));
                        }
                        ReferenceCountUtil.release(response);
                    } else {
                        logger.error("Completed exceptionally, message={}", ex.getMessage(), ex);
                    }
                    scheduler.schedule(() -> sendAndReceive(master), 5, TimeUnit.SECONDS);
                }, Modbus.sharedExecutor());
                break;
            case 5:
                master.sendRequest(new WriteSingleCoilRequest(address, false), 0);
                break;
            case 6:
                // 暂未实现
                ByteBuf coilStatus = PooledByteBufAllocator.DEFAULT.buffer();
                for (int i = 0; i < quantity; i++) {
                    coilStatus.writeShort(1);
                }
                master.sendRequest(new WriteMultipleCoilsRequest(address, quantity, coilStatus), 0);
                break;
            case 7:
                CompletableFuture<WriteSingleRegisterResponse> future7 =
                        master.sendRequest(new WriteSingleRegisterRequest(address, 45), 0);
                future7.whenCompleteAsync((response, ex) -> {
                    if (response != null) {
                        System.out.println(response.getValue());
                        ReferenceCountUtil.release(response);
                        stop();
                    } else {
                        logger.error("Completed exceptionally, message={}", ex.getMessage(), ex);
                        scheduler.schedule(() -> sendAndReceive(master), 5, TimeUnit.SECONDS);
                    }
                }, Modbus.sharedExecutor());
                break;
            case 8:
                ByteBuf registers = PooledByteBufAllocator.DEFAULT.buffer();
                for (int i = 0; i < quantity; i++) {
                    registers.writeShort(i*100);
                }
                master.sendRequest(new WriteMultipleRegistersRequest(address, quantity, registers), 0);
                break;
            default:
        }
    }

    public void stop() {
        started = false;
        masters.forEach(ModbusTcpMaster::disconnect);
        masters.clear();
    }

    /**
     * 自定义截取字符串长度
     *
     * @param str
     * @param length
     * @return
     */
    public static String[] stringToStringArray(String str, int length) {
        //获取整个字符串可以被切割成字符字串的个数
        int n = (str.length()) / length;
        String[] split = new String[n];
        for (int i = 0; i < n; i++) {
            if (i < (n - 1)) {
                split[i] = str.substring(i * length, (i + 1) * length);
            } else {
                split[i] = str.substring(i * length);
            }
        }
        return split;
    }
}
